package com.gitee.wsl.compose.animation.spec

import androidx.annotation.FloatRange
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.core.AnimationConstants
import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
import androidx.compose.animation.core.ArcAnimationSpec
import androidx.compose.animation.core.ArcMode
import androidx.compose.animation.core.ArcMode.Companion.ArcBelow
import androidx.compose.animation.core.DurationBasedAnimationSpec
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.ExperimentalAnimationSpecApi
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.KeyframesSpec
import androidx.compose.animation.core.KeyframesWithSplineSpec
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.StartOffset
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import com.gitee.wsl.compose.animation.A
import com.gitee.wsl.compose.animation.CupertinoEasing

//动画规范
val A.Spac: AnimationSpec
    get() = AnimationSpec

internal val DefaultSpec = spring(
    stiffness = Spring.StiffnessMediumLow,
    visibilityThreshold = IntOffset.VisibilityThreshold
)

/**
 * AnimationSpec 动画规范
 *  -- FloatAnimationSpac
 *     -- FloatTweenSpec
 *     -- FloatSpringSpec
 *  -- FiniteAnimationSpec 有限动画
 *     -- DurationBasedAnimationSpec 指定时长的动画
 *         -- KeyframesSpec 关键帧动画
 *         -- SnapSpec 即时生效动画
 *         -- TweenSpec 补间动画
 *     -- RepeatableSpec 实现有限次数的重复动画
 *     -- SpringSpec 弹簧效果
 *  -- InfiniteAnimationSpec 无限动画
 */

object AnimationSpec{
    internal val intSizeDefaultSpring = androidx.compose.animation.core.spring(visibilityThreshold = IntSize.VisibilityThreshold)

    internal val dpDefaultSpring = androidx.compose.animation.core.spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)

    internal val defaultAnimation = androidx.compose.animation.core.spring<Float>()
    internal val sizeDefaultSpring = androidx.compose.animation.core.spring(visibilityThreshold = Size.VisibilityThreshold)
    internal val offsetDefaultSpring = androidx.compose.animation.core.spring(visibilityThreshold = Offset.VisibilityThreshold)
    internal val rectDefaultSpring = androidx.compose.animation.core.spring(visibilityThreshold = Rect.VisibilityThreshold)
    internal val intDefaultSpring = androidx.compose.animation.core.spring(visibilityThreshold = Int.VisibilityThreshold)
    internal val intOffsetDefaultSpring = androidx.compose.animation.core.spring(visibilityThreshold = IntOffset.VisibilityThreshold)

    //TweenSpec 补间动画
    @Stable
    fun <T> tween(
        durationMillis: Int = AnimationConstants.DefaultDurationMillis,//动画时长，默认 300ms
        delayMillis: Int = 0,//动画启动延时，默认不延时
        easing: Easing = FastOutSlowInEasing
    ) = androidx.compose.animation.core.tween<T>(durationMillis, delayMillis, easing)

    /**
     * 弹簧效果
     * spring(dampingRatio = 0.05f, stiffness = 1500f, 50.dp)
     */
    @Stable
    fun <T> spring(
        dampingRatio: Float = Spring.DampingRatioNoBouncy,//阻尼比,小于1越弹，默认值是：1f，没有效果
        stiffness: Float = Spring.StiffnessMedium,//刚度.越小越弹，默认值是：1500f
        visibilityThreshold: T? = null //可视阈值,让弹窗到达这个阈值后直接停止。
    ) = androidx.compose.animation.core.spring<T>(dampingRatio, stiffness, visibilityThreshold)

    //SnapSpec 即时生效动画
    @Stable
    fun <T> snap(delayMillis: Int = 0) = androidx.compose.animation.core.snap<T>(delayMillis)

    /**
     * 分段动画
     * val value by animateIntAsState(
     *         targetValue = if (state) 300 else 100,
     *         animationSpec = keyframes {
     *             durationMillis = 2000
     *             0 at 700 with LinearOutSlowInEasing(用于之后的动画)
     *             700 at 1400 with FastOutLinearInEasing
     *             1400 at 2000
     *         })
     *
     */
    @Stable
    fun <T> keyframes(
        init: KeyframesSpec.KeyframesSpecConfig<T>.() -> Unit
    ) = androidx.compose.animation.core.keyframes<T>(init)

    @OptIn(ExperimentalAnimationSpecApi::class)
    @Stable
    fun <T> keyframesWithSpline(
        init: KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>.() -> Unit
    ) = androidx.compose.animation.core.keyframesWithSpline<T>(init)

    @ExperimentalAnimationSpecApi
    fun <T> keyframesWithSpline(
        @FloatRange(0.0, 1.0) periodicBias: Float,
        init: KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>.() -> Unit
    ) = androidx.compose.animation.core.keyframesWithSpline<T>(periodicBias, init)

    /**
     * val value by animateIntAsState(
     *         targetValue = if (state) 300 else 100,
     *         animationSpec = repeatable(
     *             iterations = 5,//动画重复执行的次数，设置多少就执行多少次
     *             animation = tween(durationMillis = 1000),
     *             repeatMode = RepeatMode.Reverse
     *         )
     *     )
     */
    //RepeatableSpec 实现有限次数的重复动画
    @Stable
    fun <T> repeatable(
        iterations: Int,//重复的次数，填 1 就是不重复
        animation: DurationBasedAnimationSpec<T>,
        repeatMode: RepeatMode = RepeatMode.Restart,//重复模式，指定是 “重启” 还是“倒放” ,倒放”， 迭代次数不能设置成双数，只能单数
        initialStartOffset: StartOffset = StartOffset(0) //初始启动偏移（时间偏移），默认值为 0，即不偏移。
    ) = androidx.compose.animation.core.repeatable<T>(iterations, animation, repeatMode, initialStartOffset)

    //InfiniteRepeatableSpec 无限次数执行动画
    @Stable
    fun <T> infiniteRepeatable(
        animation: DurationBasedAnimationSpec<T>,
        repeatMode: RepeatMode = RepeatMode.Restart,
        initialStartOffset: StartOffset = StartOffset(0)
    ) = androidx.compose.animation.core.infiniteRepeatable<T>(animation,repeatMode, initialStartOffset)

    @Stable
    @ExperimentalAnimationSpecApi
    fun <T> arc(
          mode: ArcMode = ArcBelow,
          durationMillis: Int = DefaultDurationMillis,
          delayMillis: Int = 0,
          easing: Easing = FastOutSlowInEasing // Same default as tween()
    ) = ArcAnimationSpec<T>(mode,durationMillis, delayMillis, easing)

    /**
     *指数型衰减动画，它是一种自然衰减，以固定的比例逐渐减小时，称之为指数衰减
     */
    fun <T> exponentialDecay(
        frictionMultiplier: Float = 1f,//摩擦系数，默认1f，数值越大，惯性滑动的摩擦力越大，滑动的距离越短
        absVelocityThreshold: Float = 0.1f//速度阈值的绝对值， 默认 0.1f，它是一个速度模型， 可以理解为在小于 0.1f 的时候， 动画就会停止下来，如果是 0.5， 那么速度在小于 0.5f 的时候就会停止下来，也就是停止的更早
    ) = androidx.compose.animation.core.exponentialDecay<T>(frictionMultiplier, absVelocityThreshold)


    /**
     * 样条衰减动画，是基于屏幕像素进行的动画速度衰减，当像素密度越大动画减速越快，
     * 动画的时长越短，动画惯性滑动的距离越短；可以理解屏幕像素密度越大摩擦力越大，
     * 所以惯性滑动的距离就越短，使用这个动画，在不同的设备上可能会呈现不同的效果
     */
    //fun <T> splineBasedDecay(): DecayAnimationSpec<T> = rememberSplineBasedDecay()

}


@Stable
fun<T> DurationBasedAnimationSpec<T>.repeat(
    iterations: Int,
    repeatMode: RepeatMode = RepeatMode.Restart,
    initialStartOffset: StartOffset = StartOffset(0)
) = androidx.compose.animation.core.repeatable<T>(iterations, this, repeatMode, initialStartOffset)

@Stable
fun<T> DurationBasedAnimationSpec<T>.infiniteRepeatable(
    repeatMode: RepeatMode = RepeatMode.Restart,
    initialStartOffset: StartOffset = StartOffset(0)
) = androidx.compose.animation.core.infiniteRepeatable<T>( this, repeatMode, initialStartOffset)


const val durationMillis = 360
const val smallScale = 0.9f
const val bigScale = 1.1f

val AnimationSpec.fromLeftToRightAnim: AnimatedContentTransitionScope<String>.() -> ContentTransform
    get() = {
        slideInHorizontally(
            initialOffsetX = { fullWidth -> fullWidth },
            animationSpec = tween(durationMillis)
        ) + scaleIn(
            initialScale = smallScale,
            animationSpec = tween(durationMillis)
        ) + fadeIn() togetherWith slideOutHorizontally(
            targetOffsetX = { fullWidth -> -fullWidth },
            animationSpec = tween(durationMillis)
        ) + scaleOut(
            targetScale = bigScale,
            animationSpec = tween(durationMillis)
        ) + fadeOut()
    }

val AnimationSpec.fromRightToLeft: AnimatedContentTransitionScope<String>.() -> ContentTransform
    get() = {
        slideInHorizontally(
            initialOffsetX = { fullWidth -> -fullWidth },
            animationSpec = tween(durationMillis)
        ) + scaleIn(
            initialScale = bigScale,
            animationSpec = tween(durationMillis)
        ) + fadeIn() togetherWith slideOutHorizontally(
            targetOffsetX = { fullWidth -> fullWidth },
            animationSpec = tween(durationMillis)
        ) + scaleOut(
            targetScale = smallScale,
            animationSpec = tween(durationMillis)
        ) + fadeOut()
    }

/**
 * Cupertino [tween] transition spec.
 *
 * Default values are used for iOS view transitions such as
 * UINavigationController, UIAlertController
 * */
fun <T> AnimationSpec.cupertinoTween(
    durationMillis: Int = CupertinoTransitionDuration,
    delayMillis: Int = 0,
    easing: Easing = A.Easing.CupertinoEasing
) : TweenSpec<T> = tween(
    durationMillis = durationMillis,
    easing = easing,
    delayMillis = delayMillis
)

private const val CupertinoTransitionDuration = 400
